/*  _______         ____    __         ___    ___
 * \    _  \       \    /  \  /       \   \  /   /       '   '  '
 *  |  | \  \       |  |    ||         |   \/   |         .      .
 *  |  |  |  |      |  |    ||         ||\  /|  |
 *  |  |  |  |      |  |    ||         || \/ |  |         '  '  '
 *  |  |  |  |      |  |    ||         ||    |  |         .      .
 *  |  |_/  /        \  \__//          ||    |  |
 * /_______/ynamic    \____/niversal  /__\  /____\usic   /|  .  . ibliotheque
 *                                                      /  \
 *                                                     / .  \
 * reads3m.c - Code to read a ScreamTracker 3         / / \  \
 *             module from an open file.             | <  /   \_
 *                                                   |  \/ /\   /
 * By entheh.                                         \_  /  > /
 *                                                      | \ / /
 *                                                      |  ' /
 *                                                       \__/
 */

// IT_STEREO... :o
#include <stdlib.h>
#include <string.h>

#include "dumb.h"
#include "internal/it.h"

static int it_s3m_read_sample_header(IT_SAMPLE *sample, long *offset,
                                     unsigned char *pack, int cwtv,
                                     DUMBFILE *f) {
    unsigned char type;
    int flags;

    type = dumbfile_getc(f);

    dumbfile_getnc((char *)sample->filename, 12, f);
    sample->filename[12] = 0;

    if (type > 1) {
        /** WARNING: no adlib support */
        dumbfile_skip(f, 3 + 12 + 1 + 1 + 2 + 2 + 2 + 12);
        dumbfile_getnc((char *)sample->name, 28, f);
        sample->name[28] = 0;
        dumbfile_skip(f, 4);
        sample->flags &= ~IT_SAMPLE_EXISTS;
        return dumbfile_error(f);
    }

    *offset = dumbfile_getc(f) << 20;
    *offset += dumbfile_igetw(f) << 4;

    sample->length = dumbfile_igetl(f);
    sample->loop_start = dumbfile_igetl(f);
    sample->loop_end = dumbfile_igetl(f);

    sample->default_volume = dumbfile_getc(f);

    dumbfile_skip(f, 1);

    flags = dumbfile_getc(f);

    if (flags < 0 || (flags != 0 && flags != 4))
        /* Sample is packed apparently (or error reading from file). We don't
         * know how to read packed samples.
         */
        return -1;

    *pack = flags;

    flags = dumbfile_getc(f);

    sample->C5_speed = dumbfile_igetl(f) << 1;

    /* Skip four unused bytes and three internal variables. */
    dumbfile_skip(f, 4 + 2 + 2 + 4);

    dumbfile_getnc((char *)sample->name, 28, f);
    sample->name[28] = 0;

    if (type == 0 || sample->length <= 0) {
        /* Looks like no-existy. Anyway, there's for sure no 'SCRS' ... */
        sample->flags &= ~IT_SAMPLE_EXISTS;
        return dumbfile_error(f);
    }

    if (dumbfile_mgetl(f) != DUMB_ID('S', 'C', 'R', 'S'))
        return -1;

    sample->global_volume = 64;

    sample->flags = IT_SAMPLE_EXISTS;
    if (flags & 1)
        sample->flags |= IT_SAMPLE_LOOP;

    /* The ST3 TECH.DOC is unclear on this, but IMAGO Orpheus is not. Piece of
     * crap. */

    if (flags & 2) {
        sample->flags |= IT_SAMPLE_STEREO;

        if ((cwtv & 0xF000) == 0x2000) {
            sample->length >>= 1;
            sample->loop_start >>= 1;
            sample->loop_end >>= 1;
        }
    }

    if (flags & 4) {
        sample->flags |= IT_SAMPLE_16BIT;

        if ((cwtv & 0xF000) == 0x2000) {
            sample->length >>= 1;
            sample->loop_start >>= 1;
            sample->loop_end >>= 1;
        }
    }

    sample->default_pan = 0; // 0 = don't use, or 160 = centre?

    if (sample->flags & IT_SAMPLE_LOOP) {
        if ((unsigned int)sample->loop_end > (unsigned int)sample->length)
            /*sample->flags &= ~IT_SAMPLE_LOOP;*/
            sample->loop_end = sample->length;
        else if ((unsigned int)sample->loop_start >=
                 (unsigned int)sample->loop_end)
            sample->flags &= ~IT_SAMPLE_LOOP;
        else
            /* ScreamTracker seems not to save what comes after the loop end
             * point, but rather to assume it is a duplicate of what comes at
             * the loop start point. I am not completely sure of this though.
             * It is easy to evade; simply truncate the sample.
             */
            sample->length = sample->loop_end;
    }

    // Do we need to set all these?
    sample->vibrato_speed = 0;
    sample->vibrato_depth = 0;
    sample->vibrato_rate = 0;
    sample->vibrato_waveform = IT_VIBRATO_SINE;
    sample->finetune = 0;
    sample->max_resampling_quality = -1;

    return dumbfile_error(f);
}

static int it_s3m_read_sample_data(IT_SAMPLE *sample, int ffi,
                                   unsigned char pack, DUMBFILE *f) {
    long n;

    long datasize = sample->length;
    if (sample->flags & IT_SAMPLE_STEREO)
        datasize <<= 1;

    sample->data = malloc(datasize * (sample->flags & IT_SAMPLE_16BIT ? 2 : 1));
    if (!sample->data)
        return -1;

    if (pack == 4) {
        if (_dumb_it_read_sample_data_adpcm4(sample, f) < 0)
            return -1;
    } else if (sample->flags & IT_SAMPLE_STEREO) {
        if (sample->flags & IT_SAMPLE_16BIT) {
            for (n = 0; n < datasize; n += 2)
                ((short *)sample->data)[n] = dumbfile_igetw(f);
            for (n = 1; n < datasize; n += 2)
                ((short *)sample->data)[n] = dumbfile_igetw(f);
        } else {
            for (n = 0; n < datasize; n += 2)
                ((signed char *)sample->data)[n] = dumbfile_getc(f);
            for (n = 1; n < datasize; n += 2)
                ((signed char *)sample->data)[n] = dumbfile_getc(f);
        }
    } else if (sample->flags & IT_SAMPLE_16BIT)
        for (n = 0; n < sample->length; n++)
            ((short *)sample->data)[n] = dumbfile_igetw(f);
    else
        for (n = 0; n < sample->length; n++)
            ((signed char *)sample->data)[n] = dumbfile_getc(f);

    if (dumbfile_error(f))
        return -1;

    if (ffi != 1) {
        /* Convert to signed. */
        if (sample->flags & IT_SAMPLE_16BIT)
            for (n = 0; n < datasize; n++)
                ((short *)sample->data)[n] ^= 0x8000;
        else
            for (n = 0; n < datasize; n++)
                ((signed char *)sample->data)[n] ^= 0x80;
    }

    return 0;
}

static int it_s3m_read_pattern(IT_PATTERN *pattern, DUMBFILE *f,
                               unsigned char *buffer) {
    int length;
    int buflen = 0;
    int bufpos = 0;

    IT_ENTRY *entry;

    unsigned char channel;

    /* Haha, this is hilarious!
     *
     * Well, after some experimentation, it seems that different S3M writers
     * define the format in different ways. The S3M docs say that the first
     * two bytes hold the "length of [the] packed pattern", and the packed
     * pattern data follow. Judging by the contents of ARMANI.S3M, packaged
     * with ScreamTracker itself, the measure of length _includes_ the two
     * bytes used to store the length; in other words, we should read
     * (length - 2) more bytes. However, aryx.s3m, packaged with ModPlug
     * Tracker, excludes these two bytes, so (length) more bytes must be
     * read.
     *
     * Call me crazy, but I just find it insanely funny that the format was
     * misunderstood in this way :D
     *
     * Now we can't just risk reading two extra bytes, because then we
     * overshoot, and DUMBFILEs don't support backward seeking (for a good
     * reason). Luckily, there is a way. We can read the data little by
     * little, and stop when we have 64 rows in memory. Provided we protect
     * against buffer overflow, this method should work with all sensibly
     * written S3M files. If you find one for which it does not work, please
     * let me know at entheh@users.sf.net so I can look at it.
     *
     * "for a good reason" ? What's this nonsense? -kode54
     *
     */

    length = dumbfile_igetw(f);

    if (dumbfile_error(f) || !length)
        return -1;

    pattern->n_rows = 0;
    pattern->n_entries = 0;

    /* Read in the pattern data, little by little, and work out how many
     * entries we need room for. Sorry, but this is just so funny...
     */
    for (;;) {
        unsigned char b = buffer[buflen++] = dumbfile_getc(f);

#if 1
        static const unsigned char used[8] = {0, 2, 1, 3, 2, 4, 3, 5};
        channel = b & 31;
        b >>= 5;
        pattern->n_entries++;
        if (b) {
            if (buflen + used[b] >= 65536)
                return -1;
            if (buflen + used[b] <= length)
                dumbfile_getnc((char *)buffer + buflen, used[b], f);
            else
                memset(buffer + buflen, 0, used[b]);
            buflen += used[b];
        } else {
            /* End of row */
            if (++pattern->n_rows == 64)
                break;
            if (buflen >= 65536)
                return -1;
        }
#else
        if (b == 0) {
            /* End of row */
            pattern->n_entries++;
            if (++pattern->n_rows == 64)
                break;
            if (buflen >= 65536)
                return -1;
        } else {
            static const unsigned char used[8] = {0, 2, 1, 3, 2, 4, 3, 5};
            channel = b & 31;
            b >>= 5;
            if (b) {
                pattern->n_entries++;
                if (buflen + used[b] >= 65536)
                    return -1;
                dumbfile_getnc(buffer + buflen, used[b], f);
                buflen += used[b];
            }
        }
#endif

        /* We have ensured that buflen < 65536 at this point, so it is safe
         * to iterate and read at least one more byte without checking.
         * However, now would be a good time to check for errors reading from
         * the file.
         */

        if (dumbfile_error(f))
            return -1;

        /* Great. We ran out of data, but there should be data for more rows.
         * Fill the rest with null data...
         */
        if (buflen >= length && pattern->n_rows < 64) {
            while (pattern->n_rows < 64) {
                if (buflen >= 65536)
                    return -1;
                buffer[buflen++] = 0;
                pattern->n_entries++;
                pattern->n_rows++;
            }
            break;
        }
    }

    pattern->entry = malloc(pattern->n_entries * sizeof(*pattern->entry));

    if (!pattern->entry)
        return -1;

    entry = pattern->entry;

    while (bufpos < buflen) {
        unsigned char b = buffer[bufpos++];

#if 1
        if (!(b & ~31))
#else
        if (b == 0)
#endif
        {
            /* End of row */
            IT_SET_END_ROW(entry);
            entry++;
            continue;
        }

        channel = b & 31;

        if (b & 224) {
            entry->mask = 0;
            entry->channel = channel;

            if (b & 32) {
                unsigned char n = buffer[bufpos++];
                if (n != 255) {
                    if (n == 254)
                        entry->note = IT_NOTE_CUT;
                    else
                        entry->note = (n >> 4) * 12 + (n & 15);
                    entry->mask |= IT_ENTRY_NOTE;
                }

                entry->instrument = buffer[bufpos++];
                if (entry->instrument)
                    entry->mask |= IT_ENTRY_INSTRUMENT;
            }

            if (b & 64) {
                entry->volpan = buffer[bufpos++];
                if (entry->volpan != 255)
                    entry->mask |= IT_ENTRY_VOLPAN;
            }

            if (b & 128) {
                entry->effect = buffer[bufpos++];
                entry->effectvalue = buffer[bufpos++];
                // XXX woot
                if (entry->effect && entry->effect < IT_MIDI_MACRO /*!= 255*/) {
                    entry->mask |= IT_ENTRY_EFFECT;
                    switch (entry->effect) {
                    case IT_BREAK_TO_ROW:
                        entry->effectvalue -= (entry->effectvalue >> 4) * 6;
                        break;

                    case IT_SET_CHANNEL_VOLUME:
                    case IT_CHANNEL_VOLUME_SLIDE:
                    case IT_PANNING_SLIDE:
                    case IT_GLOBAL_VOLUME_SLIDE:
                    case IT_PANBRELLO:
                    case IT_MIDI_MACRO:
                        entry->mask &= ~IT_ENTRY_EFFECT;
                        break;

                    case IT_S:
                        switch (entry->effectvalue >> 4) {
                        case IT_S_SET_PANBRELLO_WAVEFORM:
                        case IT_S_FINE_PATTERN_DELAY:
                        case IT_S7:
                        case IT_S_SET_SURROUND_SOUND:
                        case IT_S_SET_MIDI_MACRO:
                            entry->mask &= ~IT_ENTRY_EFFECT;
                            break;
                        }
                        break;
                    }
                }
                /** WARNING: ARGH! CONVERT TEH EFFECTS!@~ */
            }

            entry++;
        }
    }

    ASSERT(entry == pattern->entry + pattern->n_entries);

    return 0;
}

/** WARNING: this is duplicated in itread.c - also bad practice to use the same
 * struct name unless they are unified in a header */
/* Currently we assume the sample data are stored after the sample headers in
 * module files. This assumption may be unjustified; let me know if you have
 * trouble.
 */

#define S3M_COMPONENT_INSTRUMENT 1
#define S3M_COMPONENT_PATTERN 2
#define S3M_COMPONENT_SAMPLE 3

typedef struct S3M_COMPONENT {
    unsigned char type;
    unsigned char n;
    long offset;
    short sampfirst; /* component[sampfirst] = first sample data after this */
    short sampnext; /* sampnext is used to create linked lists of sample data */
} S3M_COMPONENT;

static int s3m_component_compare(const void *e1, const void *e2) {
    return (int)(((const S3M_COMPONENT *)e1)->offset -
                 ((const S3M_COMPONENT *)e2)->offset);
}

static DUMB_IT_SIGDATA *it_s3m_load_sigdata(DUMBFILE *f, int *cwtv) {
    DUMB_IT_SIGDATA *sigdata;

    int flags, ffi;
    int default_pan_present;

    int master_volume;

    unsigned char sample_pack[256];

    S3M_COMPONENT *component;
    int n_components = 0;

    int n;

    unsigned char *buffer;

    sigdata = malloc(sizeof(*sigdata));
    if (!sigdata)
        return NULL;

    dumbfile_getnc((char *)sigdata->name, 28, f);
    sigdata->name[28] = 0;

    n = dumbfile_getc(f);

    if (n != 0x1A && n != 0) {
        free(sigdata);
        return NULL;
    }

    if (dumbfile_getc(f) != 16) {
        free(sigdata);
        return NULL;
    }

    dumbfile_skip(f, 2);

    sigdata->song_message = NULL;
    sigdata->order = NULL;
    sigdata->instrument = NULL;
    sigdata->sample = NULL;
    sigdata->pattern = NULL;
    sigdata->midi = NULL;
    sigdata->checkpoint = NULL;

    sigdata->n_orders = dumbfile_igetw(f);
    sigdata->n_instruments = 0;
    sigdata->n_samples = dumbfile_igetw(f);
    sigdata->n_patterns = dumbfile_igetw(f);

    if (dumbfile_error(f) || sigdata->n_orders <= 0 ||
        sigdata->n_orders > 1024 || // Whoa, nelly.
        sigdata->n_samples > 256 || sigdata->n_patterns > 256) {
        _dumb_it_unload_sigdata(sigdata);
        return NULL;
    }

    sigdata->order = malloc(sigdata->n_orders);
    if (!sigdata->order) {
        _dumb_it_unload_sigdata(sigdata);
        return NULL;
    }

    if (sigdata->n_samples) {
        sigdata->sample = malloc(sigdata->n_samples * sizeof(*sigdata->sample));
        if (!sigdata->sample) {
            _dumb_it_unload_sigdata(sigdata);
            return NULL;
        }
        for (n = 0; n < sigdata->n_samples; n++)
            sigdata->sample[n].data = NULL;
    }

    if (sigdata->n_patterns) {
        sigdata->pattern =
            malloc(sigdata->n_patterns * sizeof(*sigdata->pattern));
        if (!sigdata->pattern) {
            _dumb_it_unload_sigdata(sigdata);
            return NULL;
        }
        for (n = 0; n < sigdata->n_patterns; n++)
            sigdata->pattern[n].entry = NULL;
    }

    flags = dumbfile_igetw(f);

    *cwtv = dumbfile_igetw(f);

    if (*cwtv == 0x1300) {
        /** WARNING: volume slides on every frame */
    }

    ffi = dumbfile_igetw(f);

    /** WARNING: which ones? */
    sigdata->flags = IT_OLD_EFFECTS | IT_COMPATIBLE_GXX | IT_WAS_AN_S3M;

    if (dumbfile_mgetl(f) != DUMB_ID('S', 'C', 'R', 'M')) {
        _dumb_it_unload_sigdata(sigdata);
        return NULL;
    }

    sigdata->global_volume = dumbfile_getc(f);
    if (!sigdata->global_volume || sigdata->global_volume > 64)
        sigdata->global_volume = 64;
    sigdata->speed = dumbfile_getc(f);
    if (sigdata->speed == 0)
        sigdata->speed = 6; // Should we? What about tempo?
    sigdata->tempo = dumbfile_getc(f);
    master_volume = dumbfile_getc(f); // 7 bits; +128 for stereo
    sigdata->mixing_volume = master_volume & 127;

    if (master_volume & 128)
        sigdata->flags |= IT_STEREO;

    /* Skip GUS Ultra Click Removal byte. */
    dumbfile_getc(f);

    default_pan_present = dumbfile_getc(f);

    dumbfile_skip(f, 8);

    /* Skip Special Custom Data Pointer. */
    /** WARNING: investigate this? */
    dumbfile_igetw(f);

    sigdata->n_pchannels = 0;
    /* Channel settings for 32 channels, 255=unused, +128=disabled */
    {
        int i;
        int sep = (7 * dumb_it_default_panning_separation + 50) / 100;
        for (i = 0; i < 32; i++) {
            int c = dumbfile_getc(f);
            if (!(c & (128 | 16))) { /* +128=disabled, +16=Adlib */
                if (sigdata->n_pchannels < i + 1)
                    sigdata->n_pchannels = i + 1;
                sigdata->channel_volume[i] = 64;
                sigdata->channel_pan[i] = c & 8 ? 7 + sep : 7 - sep;
                /** WARNING: ah, but it should be 7 for mono... */
            } else {
                /** WARNING: this could be improved if we support channel
                 * muting... */
                sigdata->channel_volume[i] = 0;
                sigdata->channel_pan[i] = 7;
            }
        }
    }

    /* Orders, byte each, length = sigdata->n_orders (should be even) */
    dumbfile_getnc((char *)sigdata->order, sigdata->n_orders, f);
    sigdata->restart_position = 0;

    component = malloc(768 * sizeof(*component));
    if (!component) {
        _dumb_it_unload_sigdata(sigdata);
        return NULL;
    }

    for (n = 0; n < sigdata->n_samples; n++) {
        component[n_components].type = S3M_COMPONENT_SAMPLE;
        component[n_components].n = n;
        component[n_components].offset = dumbfile_igetw(f) << 4;
        component[n_components].sampfirst = -1;
        n_components++;
    }

    for (n = 0; n < sigdata->n_patterns; n++) {
        long offset = dumbfile_igetw(f) << 4;
        if (offset) {
            component[n_components].type = S3M_COMPONENT_PATTERN;
            component[n_components].n = n;
            component[n_components].offset = offset;
            component[n_components].sampfirst = -1;
            n_components++;
        } else {
            /** WARNING: Empty 64-row pattern ... ? (this does happen!) */
            sigdata->pattern[n].n_rows = 64;
            sigdata->pattern[n].n_entries = 0;
        }
    }

    qsort(component, n_components, sizeof(S3M_COMPONENT),
          &s3m_component_compare);

    /* I found a really dumb S3M file that claimed to contain default pan
     * data but didn't contain any. Programs would load it by reading part of
     * the first instrument header, assuming the data to be default pan
     * positions, and then rereading the instrument module. We cannot do this
     * without obfuscating the file input model, so we insert an extra check
     * here that we won't overrun the start of the first component.
     */
    if (default_pan_present == 252 &&
        component[0].offset >= dumbfile_pos(f) + 32) {
        /* Channel default pan positions */
        int i;
        for (i = 0; i < 32; i++) {
            int c = dumbfile_getc(f);
            if (c & 32)
                sigdata->channel_pan[i] = c & 15;
        }
    }

    {
        int i;
        for (i = 0; i < 32; i++) {
            sigdata->channel_pan[i] -= (sigdata->channel_pan[i] & 8) >> 3;
            sigdata->channel_pan[i] = ((int)sigdata->channel_pan[i] << 5) / 7;
        }
    }

    sigdata->pan_separation = 128;

    if (dumbfile_error(f)) {
        free(component);
        _dumb_it_unload_sigdata(sigdata);
        return NULL;
    }

    buffer = malloc(65536);
    if (!buffer) {
        free(component);
        _dumb_it_unload_sigdata(sigdata);
        return NULL;
    }

    for (n = 0; n < n_components; n++) {
        long offset;
        int m;

        offset = 0;
        if (dumbfile_seek(f, component[n].offset, DFS_SEEK_SET)) {
            free(buffer);
            free(component);
            _dumb_it_unload_sigdata(sigdata);
            return NULL;
        }

        switch (component[n].type) {

        case S3M_COMPONENT_PATTERN:
            if (it_s3m_read_pattern(&sigdata->pattern[component[n].n], f,
                                    buffer)) {
                free(buffer);
                free(component);
                _dumb_it_unload_sigdata(sigdata);
                return NULL;
            }
            break;

        case S3M_COMPONENT_SAMPLE:
            if (it_s3m_read_sample_header(&sigdata->sample[component[n].n],
                                          &offset, &sample_pack[component[n].n],
                                          *cwtv, f)) {
                free(buffer);
                free(component);
                _dumb_it_unload_sigdata(sigdata);
                return NULL;
            }

            if (sigdata->sample[component[n].n].flags & IT_SAMPLE_EXISTS) {
                short *sample;

                for (m = n + 1; m < n_components; m++)
                    if (component[m].offset > offset)
                        break;
                m--;

                sample = &component[m].sampfirst;

                while (*sample >= 0 && component[*sample].offset <= offset)
                    sample = &component[*sample].sampnext;

                component[n].sampnext = *sample;
                *sample = n;

                component[n].offset = offset;
            }
        }

        m = component[n].sampfirst;

        while (m >= 0) {
            // XXX
            if (dumbfile_seek(f, component[m].offset, DFS_SEEK_SET)) {
                free(buffer);
                free(component);
                _dumb_it_unload_sigdata(sigdata);
                return NULL;
            }

            if (it_s3m_read_sample_data(&sigdata->sample[component[m].n], ffi,
                                        sample_pack[component[m].n], f)) {
                free(buffer);
                free(component);
                _dumb_it_unload_sigdata(sigdata);
                return NULL;
            }

            m = component[m].sampnext;
        }
    }

    free(buffer);
    free(component);

    if (_dumb_it_fix_invalid_orders(sigdata) < 0) {
        _dumb_it_unload_sigdata(sigdata);
        return NULL;
    }

    return sigdata;
}

static char hexdigit(int in) {
    if (in < 10)
        return in + '0';
    else
        return in + 'A' - 10;
}

DUH *dumb_read_s3m_quick(DUMBFILE *f) {
    sigdata_t *sigdata;
    int cwtv;

    DUH_SIGTYPE_DESC *descptr = &_dumb_sigtype_it;

    sigdata = it_s3m_load_sigdata(f, &cwtv);

    if (!sigdata)
        return NULL;

    {
        char version[8];
        const char *tag[3][2];
        tag[0][0] = "TITLE";
        tag[0][1] = (const char *)(((DUMB_IT_SIGDATA *)sigdata)->name);
        tag[1][0] = "FORMAT";
        tag[1][1] = "S3M";
        tag[2][0] = "TRACKERVERSION";
        version[0] = hexdigit((cwtv >> 8) & 15);
        version[1] = '.';
        version[2] = hexdigit((cwtv >> 4) & 15);
        version[3] = hexdigit(cwtv & 15);
        version[4] = 0;
        tag[2][1] = (const char *)&version;
        return make_duh(-1, 3, (const char *const(*)[2])tag, 1, &descptr,
                        &sigdata);
    }
}
